package com.ikas.ai.utils;

import org.apache.commons.lang3.StringUtils;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.zip.*;

/**
 * 压缩、解压常用类
 * @author HXH
 */
@SuppressWarnings("all")
public class ZipUtil {

    private static Logger logger = LoggerFactory.getLogger(ZipUtil.class);
    private static final int BUFFEREDSIZE = 512;
    private static final String ENCODING = "UTF-8";

    private ZipUtil() {

    }

    /***
     * 压缩GZip
     * @param data
     * @return
     */
    public static byte[] gZip(byte[] data) {
        byte[] b = null;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            GZIPOutputStream gzip = new GZIPOutputStream(bos);
            gzip.write(data);
            gzip.finish();
            gzip.close();
            b = bos.toByteArray();
            bos.close();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return b;
    }

    /***
     * 解压GZip
     * @param data
     * @return
     */
    public static byte[] unGZip(byte[] data) {
        byte[] b = null;
        try {
            ByteArrayInputStream bis = new ByteArrayInputStream(data);
            GZIPInputStream gzip = new GZIPInputStream(bis);
            byte[] buf = new byte[1024];
            int num = -1;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while ((num = gzip.read(buf, 0, buf.length)) != -1) {
                baos.write(buf, 0, num);
            }
            b = baos.toByteArray();
            baos.flush();
            baos.close();
            gzip.close();
            bis.close();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return b;
    }

    // #################################################################################

    /***
     * 压缩Zip
     * @param data
     * @return
     */
    public static byte[] zip(byte[] data) {
        byte[] b = null;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ZipOutputStream zip = new ZipOutputStream(bos);
            ZipEntry entry = new ZipEntry("zip");
            entry.setSize(data.length);
            zip.putNextEntry(entry);
            zip.write(data);
            zip.closeEntry();
            zip.close();
            b = bos.toByteArray();
            bos.close();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return b;
    }

    /***
     * 解压Zip
     * @param data
     * @return
     */
    public static byte[] unZip(byte[] data) {
        byte[] b = null;
        try {
            ByteArrayInputStream bis = new ByteArrayInputStream(data);
            ZipInputStream zip = new ZipInputStream(bis);
            while (zip.getNextEntry() != null) {
                byte[] buf = new byte[1024];
                int num = -1;
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                while ((num = zip.read(buf, 0, buf.length)) != -1) {
                    baos.write(buf, 0, num);
                }
                b = baos.toByteArray();
                baos.flush();
                baos.close();
            }
            zip.close();
            bis.close();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return b;
    }

    /**
     * 压缩
     *
     * @param data 待压缩数据
     * @return byte[] 压缩后的数据
     */
    public static byte[] compress(byte[] data) {
        byte[] output = new byte[0];
        Deflater compresser = new Deflater();
        compresser.reset();
        compresser.setInput(data);
        compresser.finish();
        ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
        try {
            byte[] buf = new byte[1024];
            while (!compresser.finished()) {
                int i = compresser.deflate(buf);
                bos.write(buf, 0, i);
            }
            output = bos.toByteArray();
        } catch (Exception e) {
            output = data;
            logger.error(e.getMessage(), e);
        } finally {
            try {
                bos.close();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
        compresser.end();
        return output;
    }

    /**
     * 解压缩
     *
     * @param data 待压缩的数据
     * @return byte[] 解压缩后的数据
     */
    public static byte[] decompress(byte[] data) {
        byte[] output = new byte[0];
        Inflater decompresser = new Inflater();
        decompresser.reset();
        decompresser.setInput(data);
        ByteArrayOutputStream o = new ByteArrayOutputStream(data.length);
        try {
            byte[] buf = new byte[1024];
            while (!decompresser.finished()) {
                int i = decompresser.inflate(buf);
                o.write(buf, 0, i);
            }
            output = o.toByteArray();
        } catch (Exception e) {
            output = data;
            logger.error(e.getMessage(), e);
        } finally {
            try {
                o.close();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
        decompresser.end();
        return output;
    }

    /**
     * 压缩文件
     *
     * @param filePath 待压缩的文件路径
     */
    public static void zip(String filePath) {
        zip(filePath, null, true);
    }

    /**
     * 压缩文件或者文件夹.
     *
     * @param source
     * @param zipName
     * @param needGetParent
     * @return
     */
    public static String zip(String source, String zipName, boolean needGetParent) {
        // 压缩文件的保存路径
        String zipFile = null;
        try {
            byte[] b = new byte[BUFFEREDSIZE];
            // 获得相关路径
            File file = new File(source);
            String destPath = file.getPath();
            // 压缩包是否需要包含上层目录结构
            if (needGetParent) {
                destPath = file.getParent();
            }
            String[] ss = file.getName().split("\\.");

            if (StringUtils.isBlank(zipName)) {
                zipFile = destPath + File.separator + ss[0] + ".zip";
            } else {
                zipFile = destPath + File.separator + zipName + ".zip";
            }
            List<String> fileList = new ArrayList<>();
            getFiles(file, fileList);
            try (FileOutputStream fileOutputStream = new FileOutputStream(zipFile);
                 CheckedOutputStream cs = new CheckedOutputStream(fileOutputStream, new CRC32());
                 ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(cs))) {
                // 指定编码为gbk，否则部署到linux下会出现乱码
                out.setEncoding(ENCODING);
                for (int i = 0; i < fileList.size(); i++) {
                    String path = fileList.get(i);
                    try (InputStream in = new FileInputStream(path)) {
                        String fileName = path.replace(destPath, "").replace(File.separatorChar, '/');
                        fileName = fileName.substring(fileName.indexOf('/') + 1);
                        ZipEntry entry = new ZipEntry(fileName);
                        entry.setUnixMode(644); // 解决linux乱码
                        out.putNextEntry(entry);
                        int len = 0;
                        while ((len = in.read(b)) != -1) {
                            out.write(b, 0, len);
                        }
                        out.closeEntry();
                        cs.flush();
                        fileOutputStream.flush();
                    }
                }
                out.flush();
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return zipFile;
    }

    /**
     * 获取需要压缩的所有文件
     *
     * @param file
     * @param list
     */
    private static void getFiles(File file, List<String> list) {
        if (file.isDirectory()) {
            for (File tmp : file.listFiles()) {
                getFiles(tmp, list);
            }
        } else {
            list.add(file.getPath());
        }
    }

    /**
     * 解压缩.
     * @param source 压缩文件路径
     * @param flag 如果需要使用压缩包名作为父级目录为true，否则为false
     */
    public static void unZip(String source,boolean isRoot) {
        // 获得相关路径
        File file = new File(source);
        String[] ss = file.getName().split("\\.");
        String parentPath = file.getParent();
        // 输出目录
        String outputDirectory = parentPath ;
        if(isRoot){
            outputDirectory += File.separatorChar + ss[0];
        }
        try (ZipFile zipFile = new ZipFile(source, ENCODING)) {
            Enumeration<?> e = zipFile.getEntries();
            ZipEntry zipEntry = null;
            createDirectory(outputDirectory, "");
            while (e.hasMoreElements()) {
                zipEntry = (ZipEntry) e.nextElement();
                if (zipEntry.isDirectory()) {
                    String name = zipEntry.getName();
                    name = name.substring(0, name.length() - 1);
                    File f = new File(outputDirectory + File.separator + name);
                    f.mkdir();
                } else {
                    String fileName = zipEntry.getName();
                    fileName = fileName.replace('\\', '/');
                    if (fileName.indexOf('/') != -1) {
                        createDirectory(outputDirectory, fileName.substring(0, fileName.lastIndexOf('/')));
                    }
                    String fullPath = outputDirectory + File.separator + zipEntry.getName();
                    File f = new File(fullPath);
                    File pf = new File(f.getParent());
                    if (!pf.exists()) {
                        pf.mkdirs();
                    }
                    if (f.createNewFile()) {
                        InputStream in = zipFile.getInputStream(zipEntry);
                        try (FileOutputStream out = new FileOutputStream(f)) {
                            byte[] by = new byte[BUFFEREDSIZE];
                            int c;
                            while ((c = in.read(by)) != -1) {
                                out.write(by, 0, c);
                            }
                            out.flush();
                        }
                        in.close();
                    } else {
                        logger.error("Failed to create {}.", fullPath);
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }


    /**
     * 解压创建目录.
     *
     * @param directory    目录
     * @param subDirectory 子目录
     */
    private static void createDirectory(String directory, String subDirectory) {
        File file = new File(directory);
        if (!file.exists()) {
            file.mkdirs();
        }
        File subfile = new File(directory + File.separatorChar + subDirectory);
        if (!subfile.exists()) {
            subfile.mkdirs();
        }
    }

    /**
     * 获取zip压缩文件下文件夹名称
     *
     * @param zipPath
     * @return
     */
    public static Set<String> getFiles(String zipPath) throws Exception {
        //获取文件输入流
        InputStream input = new FileInputStream(zipPath);
        Set<String> result = getFiles(input);
        return result;
    }

    /**
     * 获取zip压缩文件下文件夹名称集合
     * @param input
     * @return
     * @throws Exception
     */
    public static Set<String> getFiles(InputStream input) throws Exception {
        Set<String> result = new HashSet<>();
        //获取ZIP输入流(一定要指定字符集Charset.forName("GBK")否则会报java.lang.IllegalArgumentException: MALFORMED)
        ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(input), Charset.forName("GBK"));
        //定义ZipEntry置为null,避免由于重复调用zipInputStream.getNextEntry造成的不必要的问题
        java.util.zip.ZipEntry ze = null;
        //循环遍历
        while ((ze = zipInputStream.getNextEntry()) != null) {
            String name = ze.getName();
            long size = ze.getSize();
            if (size == 0) {
                //logger.info("文件名：" + name + " 文件大小：" + size + " bytes");
                String str = name.substring(0, name.indexOf("/"));
                result.add(str);
            }
        }
        //一定记得关闭流
        zipInputStream.closeEntry();
        input.close();
        return result;
    }

}
